#include <iostream>   // 输入输出流库
#include <vector>     // 使用 vector 容器存储数据
#include <algorithm>  // 使用 sort 函数对边按权值排序

using namespace std;

// 定义图中的边结构体
struct Edge {
    int u, v, cost;  // 边的两个顶点 u 和 v，以及这条边的费用 cost

    // 重载小于运算符，用于排序时按照 cost 升序排列
    bool operator<(const Edge& other) const {
        return cost < other.cost;
    }
};

// 并查集类（Union-Find），用于快速判断两个节点是否连通
class UnionFind {
private:
    vector<int> parent;  // 每个节点的父节点
public:
    // 构造函数：初始化每个节点的父节点为自己
    UnionFind(int n) {
        parent.resize(n + 1);  // 节点编号从 1 开始，所以大小为 n+1
        for (int i = 1; i <= n; ++i)
            parent[i] = i;     // 初始化并查集
    }

    // 查找根节点，并进行路径压缩优化
    int find(int x) {
        if (parent[x] != x)
            parent[x] = find(parent[x]); // 递归查找根节点并将路径上的节点直接指向根
        return parent[x];
    }

    // 合并两个集合（将 x 所在集合合并到 y 所在集合）
    void unite(int x, int y) {
        int rootX = find(x);  // 找到 x 的根
        int rootY = find(y);  // 找到 y 的根
        if (rootX != rootY)
            parent[rootX] = rootY;  // 将 x 的根连接到 y 的根上
    }
};

int main() {
    int n, m;              // n: 麦田数量，m: 可建水渠的数量
    cin >> n >> m;         // 输入 n 和 m

    vector<Edge> edges(m); // 创建边数组，保存所有可修建的水渠信息
    for (int i = 0; i < m; ++i) {
        // 输入每条边的信息：u、v、cost
        cin >> edges[i].u >> edges[i].v >> edges[i].cost;
    }

    // 将所有边按照费用从小到大排序，以贪心方式构建 MST
    sort(edges.begin(), edges.end());

    UnionFind uf(n);       // 初始化并查集，总共有 n 个节点
    int totalCost = 0;     // 记录最小生成树的总费用
    int edgeCount = 0;     // 已选边的数量，生成树需要 n - 1 条边

    // 遍历所有边，尝试加入最小生成树
    for (const auto& e : edges) {
        // 如果两个节点不在同一个集合中，说明加入这条边不会形成环
        if (uf.find(e.u) != uf.find(e.v)) {
            uf.unite(e.u, e.v);           // 合并两个集合
            totalCost += e.cost;          // 累加这条边的费用
            edgeCount++;                  // 已选择的边数增加
            if (edgeCount == n - 1) break; // 当已经选择了 n-1 条边，生成树完成，提前退出循环
        }
    }

    // 输出最终结果：连接所有麦田所需的最小费用
    cout << totalCost << endl;

    return 0;
}